package com.cy;

import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * 服务的消费方
 */

@EnableFeignClients  //启动feign方式的调用
@SpringBootApplication
public class NacosConsumerApplication {
    //3.链路模式限流:新增配置 CommonFilter就在上面的依赖里
    @Configuration
    public class FilterContextConfig {
        @Bean
        public FilterRegistrationBean sentinelFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new CommonFilter());
            registration.addUrlPatterns("/*");
            // 入口资源关闭聚合
            registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
            registration.setName("sentinelFilter");
            registration.setOrder(1);
            return registration;
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(NacosConsumerApplication.class, args);
    }
    /**
     * @Bean注解由spring框架提供，通常应用于@Configration注解描述的类中， 用于描述方法，将方法的返回值交给spring管理。
     * 初始化RestTemplate对象(Spring提供)，基于此对象
     * 可以通过http协议调用远程服务
     */
/**
 初始化RestTemplate对象--(是Spring提供的方法，但Spring中默认是没有RestTemplate对象的，
 我们要new一个RestTemplate对象交给spring管理，放到bean池中，
 而new的对象名(restTemplate)尽量和RestTemplate保持一样)，
 *** RestTemplate作用：一个进程(消费方)调用另一个进程(提供方)的内容--
 用户访问服务消费方(通过http协议),而RestTemplate会采用Restful风格去调用服务提供方的内容给消费方
 */
    /**
     * REST 是一种互联网的系统架构风格
     * 而RestTemplate字面意思即支持Rest风格架构的互联网的请求方式模板
     * 它是一种支持REST风格并简化了发起http请求访问资源以及处理响应的方式
     */
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    /**
     * @LoadBalanced描述RestTemplate时，底层再基于RestTemplate进行服务 调用时，会被一个负载均衡拦截器进行拦截，然后再基于特定的负载均衡算法
     * 进行服务调用
     */
    @Bean
    @LoadBalanced  //负载均衡实现-方法2：也可以不用LoadBalancerClient，加个注解@LoadBalanced实现负载均衡
    public RestTemplate loadBalancedRestTemplate() {
        return new RestTemplate();
    }

    @Value("${spring.application.name}")
    private String applicationName;


    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RestTemplate loadBalancedRestTemplate;

    /**
     * LoadBalancerClient---就是负载均衡客户端
     * LoadBalancerClient取=寻找服务的简单流程:
     * Ribbon 里面封装了 获取全部服务的接口(getallserverlists的方法), 其中通过filter过滤一部分服务,
     * 然后, Ribbon 的IRule对象, 选择一个有效的服务返回给调用方,springcloud提供了LoadBalancerClient,
     * 它继承了ribbon的接口.我们之间引入这个LoadBalancerClient就可以进行服务的查找
     */
    //loadBalancerClient--微服务提供的
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /*链路模式
只记录链路入口的流量。也就是当多个服务对指定资源调用时，假如流量超出了指定阈值，则进行限流。
被调用的方法用@SentinelResource进行注解，然后分别用不同业务方法对此业务进行调用*/
    @Service
    public class ConsumerService {
        @SentinelResource("doGetResource")
        public String doGetResource() {
            return "doGetResource";
        }
    }

    @RestController
    public class ConsumerController {
        @Autowired //把链路方法注入
        private ConsumerService consumerService;

        //定义热点业务代码
        //***单机阈值---即每秒只能通过多少请求数，超出则限流熔断
        @GetMapping("/consumer/doFindById")
        @SentinelResource("doFindById")
/**@RequestParam是传递参数的.
 @RequestParam用于将输入的url请求中的数据(?id=10)传入到方法的参数中(id=10)。 required = false---设置@RequestParam可以传值，也可以不传值，不设置就必须传值*/
        public String doFindById(@RequestParam(value = "id", required = false) Integer id,
                                 @RequestParam(value = "name", required = false) String name) {
            return "hot id is " + id;
        }

        //方法2：用@LoadBalanced注解实现负载均衡
        @GetMapping("/consumer/doLoadBalanced")
        public String doRestLoadBalancedEcho() {
            consumerService.doGetResource();
            //假如RestTemplate对象由LoadBalanced注解描述时，在
            //编写url时，应该用“服务名”替换ip和port--可以负载均衡切换，不用特定的ip和port写死
            String url = String.format("http://%s/provider/echo/%s", "nacos-provider", applicationName);
            return loadBalancedRestTemplate.getForObject(url, String.class);
        }

        //方法1:用loadBalancerClient实现负载均衡
        @GetMapping("/consumer/doRestEcho")
        public String doRestEcho() {
            consumerService.doGetResource();
//基于服务名获取服务实例
            ServiceInstance serviceInstance =
                    loadBalancerClient.choose("nacos-provider");
//获取服务提供方实例的ip地址
            String host = serviceInstance.getHost();
//获取服务提供方实例的端口号
            int port = serviceInstance.getPort();

//手写的url太难看了，通过java里的String.format()方法的--占位符"%s"的形式灵活构建url
            String url = String.format("http://%s:%s/provider/echo/%s", host, port, applicationName);
            //String url="http://localhost:8081/provider/echo/"+applicationName;
            System.out.println("url:" + url);
            return restTemplate.getForObject(url, String.class);
        }
    }
}